<?php
// +----------------------------------------------------------------------
// | sfadmin - Suifeng Administration
// +----------------------------------------------------------------------
// | Copyright (c) 2017-2020 http://suifeng.tech All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: suifeng <1787585212@qq.com>
// +----------------------------------------------------------------------

namespace app\install\controller;

/**
 * 安装控制器
 */
class Index extends \app\common\controller\BaseController
{
    // 初始化方法
    protected function _initialize()
    {
        $no_verify = array('index', 'step1', 'complete');
        if (in_array(request()->action(), $no_verify)) {
            return true;
        }
        if (is_file(CONF_PATH . 'config' . CONF_EXT)) {
            $this->error('已经成功安装了本系统，请不要重复安装!', __ROOT__ . '/index.php');
        }
    }

    // 安装首页
    public function index()
    {
        $this->redirect('install/index/step1');
    }

    // 安装第一步，同意安装协议
    public function step1()
    {
        session('step', '1');
        session('error', false);
        $this->assign('meta_title', "step1");
        return $this->fetch();
    }

    // 安装第二步，检测运行所需的环境设置
    public function step2()
    {
        if (request()->isPost()) {
            if (session('error')) {
                $this->error('环境检测没有通过，请调整环境后重试！');
            } else {
                $this->success('恭喜您环境检测通过', url('step3'));
            }
        } else {
            session('step', '2');
            session('error', false);

            //环境检测
            $this->assign('check_env', $this->_check_env());

            //目录文件读写检测
            $this->assign('check_dirfile', $this->_check_dirfile());

            //函数及扩展库检测
            $this->assign('check_func_and_ext', $this->_check_func_and_ext());

            $this->assign('meta_title', "step2");
            return $this->fetch();
        }
    }

    // 安装第三步，创建数据库
    public function step3($db = null)
    {
        if (request()->isPost()) {
            //检测数据库配置
            if (!is_array($db) || empty($db['type']) || empty($db['hostname'])
                || empty($db['hostport']) || empty($db['database'])
                || empty($db['username']) || empty($db['prefix'])) {
                $this->error('请填写完整的数据库配置');
            } else {
                //缓存数据库配置
                session('db_config', $db);

                //创建数据库连接
                $db_name = $db['database'];
                $db['database'] = 'information_schema'; // 防止不存在的数据库导致连接数据库失败
                $db_instance = \think\Db::connect($db);

                //检测数据库连接
                $result1 = $db_instance->query('select version()');
                if (!$result1) {
                    $this->error('数据库连接失败，请检查数据库配置！');
                }

                //用户选择不覆盖情况下检测是否已存在数据库
                if (input('post.cover') === '0') {
                    //检测是否已存在数据库
                    $result2 = $db_instance->query('SELECT * FROM information_schema.schemata WHERE schema_name="' . $db_name . '"');
                    if ($result2) {
                        $this->error('该数据库已存在，请更换名称！如需覆盖，请选中覆盖按钮！');
                    }

                    //创建数据库
                    $sql = "CREATE DATABASE IF NOT EXISTS `{$db_name}` DEFAULT CHARACTER SET utf8";
                    $db_instance->execute($sql) || $this->error($db_instance->getError());
                }
            }

            //跳转到数据库安装页面
            $this->success('参数正确开始安装', url('step4'));
        } else {
            session('step', '3');
            session('error', false);
            $rand = \suifeng\helper\StringHelper::randString(6, 3); //生成随机数
            $this->assign('meta_title', "step3");
            return $this->fetch();
        }
    }

    // 安装第四步，安装数据表，创建配置文件
    public function step4()
    {
        session('step', '4');
        session('error', false);
        $this->assign('meta_title', "step4");
        echo $this->fetch();

        //连接数据库
        $db_config   = session('db_config');
        $db_instance = \think\Db::connect($db_config + array(
        		'params' => ['MYSQL_ATTR_USE_BUFFERED_QUERY'=>true]
        ));

        //创建数据表
        $this->_create_tables($db_instance, $db_config['prefix']);

        //生成加密字符串
        $add_chars = '`~!@#$%^&*()_+-=[]{};:"|,.<>/?';
        $auth = \suifeng\helper\StringHelper::randString(64, '', $add_chars); //生成随机数

        //创建配置文件
        $conf = $this->_write_config($db_config, $auth);

        //根据加密字符串更新admin密码的加密结果
        $new_admin_password = user_md5('admin', $auth);
        $sql                = <<<SQL
        UPDATE `{$db_config["prefix"]}admin_user` SET `password`='{$new_admin_password}' WHERE `id` = 1;
SQL;
        $result = $db_instance->execute($sql);
        if (!$result) {
            $this->error('管理员新密码设置出错！');
        }
        
        // 清除缓存
        $result = \think\Cache::clear();
        if (!$result) {
            $this->error('清除缓存出错！');
        }
        
        if (session('error')) {
            $this->error('安装出错', 'index');
        } else {
            echo "<script type=\"text/javascript\">window.location.href=\"" . url('complete') . "\"</script>";
            flush();
            ob_flush();
        }
    }

    // 安装完成
    public function complete()
    {
        if (session('step') !== '4') {
            $this->error('请正确安装系统', url('step1'));
        }

        //写入安装锁定文件(只能在最后一步写入锁定文件，因为锁定文件写入后安装模块将无法访问)
        //file_put_contents(RUNTIME_PATH . 'data/install.lock', 'lock');

        session('step', null);
        session('error', null);
        $this->assign('meta_title', "完成");
        return $this->fetch();
    }
    
    /**
     * 系统环境检测
     * @return array 系统环境数据
     */
    protected function _check_env()
    {
    	$items = array(
    			'os'     => array(
    					'title'   => '操作系统',
    					'limit'   => '不限制',
    					'current' => PHP_OS,
    					'icon'    => 'fa-check text-success',
    			),
    			'php'    => array(
    					'title'   => 'PHP版本',
    					'limit'   => '5.4+',
    					'current' => PHP_VERSION,
    					'icon'    => 'fa-check text-success',
    			),
    			'upload' => array(
    					'title'   => '附件上传',
    					'limit'   => '不限制',
    					'current' => ini_get('file_uploads') ? ini_get('upload_max_filesize') : '未知',
    					'icon'    => 'fa-check text-success',
    			),
    			'gd'     => array(
    					'title'   => 'GD库',
    					'limit'   => '2.0+',
    					'current' => '未知',
    					'icon'    => 'fa-check text-success',
    			),
    			'disk'   => array(
    					'title'   => '磁盘空间',
    					'limit'   => '200M+',
    					'current' => '未知',
    					'icon'    => 'fa-check text-success',
    			),
    	);
    
    	//PHP环境检测
    	if ($items['php']['current'] < 5.4) {
    		$items['php']['icon'] = 'fa-remove text-danger';
    		session('error', true);
    	}
    
    	//GD库检测
    	$tmp = function_exists('gd_info') ? gd_info() : array();
    	if (!$tmp['GD Version']) {
    		$items['gd']['current'] = '未安装';
    		$items['gd']['icon']    = 'fa-remove text-danger';
    		session('error', true);
    	} else {
    		$items['gd']['current'] = $tmp['GD Version'];
    	}
    	unset($tmp);
    
    	//磁盘空间检测
    	if (function_exists('disk_free_space')) {
    		$disk_size                = floor(disk_free_space('./') / (1024 * 1024)) . 'M';
    		$items['disk']['current'] = $disk_size . 'MB';
    		if ($disk_size < 200) {
    			$items['disk']['icon'] = 'fa-remove text-danger';
    			session('error', true);
    		}
    	}
    
    	return $items;
    }
    
    /**
     * 目录，文件读写检测
     * @return array 检测数据
     */
    protected function _check_dirfile()
    {
    	$items = array(
    			'0' => array(
    					'type'  => 'dir',
    					'path'  => RUNTIME_PATH,
    					'title' => '可写',
    					'icon'  => 'fa-check text-success',
    			),
    			'1' => array(
    					'type'  => 'dir',
    					'path'  => RUNTIME_PATH . 'uploads',
    					'title' => '可写',
    					'icon'  => 'fa-check text-success',
    			),
    			'2' => array(
    					'type'  => 'dir',
    					'path'  => RUNTIME_PATH . 'data',
    					'title' => '可写',
    					'icon'  => 'fa-check text-success',
    			),
    			'3' => array(
    					'type'  => 'file',
    					'path'  => CONF_PATH . 'config' . CONF_EXT,
    					'title' => '可写',
    					'icon'  => 'fa-check text-success',
    			),
    	);
    
    	foreach ($items as &$val) {
    		$path = $val['path'];
    		if ('dir' === $val['type']) {
    			if (!is_writable($path)) {
    				if (is_dir($path)) {
    					$val['title'] = '不可写';
    					$val['icon']  = 'fa-remove text-danger';
    					session('error', true);
    				} else {
    					mkdir($path, 0777, true); //尝试创建目录
    					if (!is_dir($path)) {
	    					$val['title'] = '不存在';
	    					$val['icon']  = 'fa-remove text-danger';
	    					session('error', true);
    					}
    				}
    			}
    		} else {
    			if (file_exists($path)) {
    				if (!is_writable($path)) {
    					$val['title'] = '不可写';
    					$val['icon']  = 'fa-remove text-danger';
    					session('error', true);
    				}
    			} else {
    				if (!is_writable(dirname($path))) {
    					$val['title'] = '不存在';
    					$val['icon']  = 'fa-remove text-danger';
    					session('error', true);
    				}
    			}
    		}
    	}
    	return $items;
    }
    
    /**
     * 函数检测
     * @return array 检测数据
     */
    protected function _check_func_and_ext()
    {
    	$items = array(
    			'0' => array(
    					'type'    => 'ext',
    					'name'    => 'pdo',
    					'title'   => '支持',
    					'current' => extension_loaded('pdo'),
    					'icon'    => 'fa-check text-success',
    			),
    			'1' => array(
    					'type'    => 'ext',
    					'name'    => 'pdo_mysql',
    					'title'   => '支持',
    					'current' => extension_loaded('pdo_mysql'),
    					'icon'    => 'fa-check text-success',
    			),
    			'2' => array(
    					'type'  => 'func',
    					'name'  => 'file_get_contents',
    					'title' => '支持',
    					'icon'  => 'fa-check text-success',
    			),
    			'3' => array(
    					'type'  => 'func',
    					'name'  => 'mb_strlen',
    					'title' => '支持',
    					'icon'  => 'fa-check text-success',
    			),
    	);
    	foreach ($items as &$val) {
    		switch ($val['type']) {
    			case 'ext':
    				if (!$val['current']) {
    					$val['title'] = '不支持';
    					$val['icon']  = 'fa-remove text-danger';
    					session('error', true);
    				}
    				break;
    			case 'func':
    				if (!function_exists($val['name'])) {
    					$val['title'] = '不支持';
    					$val['icon']  = 'fa-remove text-danger';
    					session('error', true);
    				}
    				break;
    		}
    	}
    
    	return $items;
    }
    
    /**
     * 创建数据表
     * @param  resource $db 数据库连接资源
     */
    protected function _create_tables($db, $prefix = '')
    {
    	//读取SQL文件
    	$sql = file_get_contents(APP_PATH . 'install/data/install.sql');
    	$sql = str_replace("\r", "\n", $sql);
    	$sql = explode(";\n", $sql);
    
    	//替换表前缀
    	$sql     = str_replace(" `sf_", " `{$prefix}", $sql);
    
    	//开始安装
    	$this->_show_msg('开始安装数据库...');
    	foreach ($sql as $value) {
    		$value = trim($value);
    		if (empty($value)) {
    			continue;
    		}
    
    		if (substr($value, 0, 12) == 'CREATE TABLE') {
    			$name = preg_replace("/^CREATE TABLE `(\w+)` .*/s", "\\1", $value);
    			$msg  = "创建数据表{$name}";
    			if (false !== $db->execute($value)) {
    				$this->_show_msg($msg . '...成功');
    			} else {
    				$this->_show_msg($msg . '...失败！', 'error');
    				session('error', true);
    			}
    		} else {
    			$db->execute($value);
    		}
    	}
    }
    
    /**
     * 写入配置文件
     * @param  array $config 配置信息
     */
    protected function _write_config($config, $auth)
    {
    	if (is_array($config)) {
    		//读取配置内容
    		$conf = file_get_contents(APP_PATH . 'install/data/config.tpl');
    
    		//替换配置项
    		foreach ($config as $name => $value) {
    			$conf = str_replace("[DB_" . strtoupper($name) . "]", $value, $conf);
    		}
    		$conf = str_replace('[AUTH_KEY]', $auth, $conf);
    
    		//写入应用配置文件
    		if (file_put_contents(CONF_PATH . 'config' . CONF_EXT, $conf)) {
    			$this->_show_msg('配置文件写入成功');
    		} else {
    			$this->_show_msg('配置文件写入失败！', 'error');
    			session('error', true);
    		}
    		return true;
    	}
    }
    
    /**
     * 及时显示提示信息
     * @param  string $msg 提示信息
     */
    protected function _show_msg($msg, $class = '')
    {
    	echo "<script type=\"text/javascript\">showmsg(\"{$msg}\", \"{$class}\")</script>";
    	flush();
    	ob_flush();
    }
}
